package com.itu.modernai;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;



public class GeneManager {
    static enum SELECTION{ELITISM, ROULETTEWHEEL, TOURNAMENT};
    static enum CROSSOVER{SINGLE_POINT, TWO_POINT, MULTI_POINT};
    
    SELECTION selectionMode = SELECTION.ROULETTEWHEEL;
    CROSSOVER crossoverMode = CROSSOVER.TWO_POINT;
    
    static int NextGene = 0;
    
    private boolean outputFitness = true;
    
    static int MAX_GENERATIONS = 1000;
    static int POPULATION_SIZE = 30;
    static float MUTATION_RATE = 0.07f;
    static float CROSSOVER_RATE = 0.7f;
    
    static float m_totalFitnessScore = 0;
    List<Gene> m_population;
    
    /**
     * 1. sort genes in population.
     * 2. remove bad half.
     * 3. reproduce from the best genes.
     * 4. mutate if mutation rate is met.
     **/
    void evaluateGeneration(){
        m_totalFitnessScore = 0;
        for(int i=0; i<POPULATION_SIZE; i++){
            getGene(i).updateFitness();
            m_totalFitnessScore += getGene(i).getFitness();
        }
    }
    
    void produceNextGeneration(){       
        //det hér er også galt. skal vidst være i en anden funktion.
        Gene[] newcomers = new Gene[POPULATION_SIZE];
        int numberOfChildren = 0;
        
        //ELITISM
        if(selectionMode == SELECTION.ELITISM){
            elitism();
        }else {
        
            while(numberOfChildren < POPULATION_SIZE){
                Gene[] parents = new Gene[2];
                if(selectionMode == SELECTION.ROULETTEWHEEL){
                    parents[0] = rouletteWheelSelection();
                    parents[1] = rouletteWheelSelection();
                } else if(selectionMode == SELECTION.TOURNAMENT){
                    parents[0] = tournamentSelection(new Random().nextInt(POPULATION_SIZE-1));
                    parents[1] = tournamentSelection(new Random().nextInt(POPULATION_SIZE-1));
                }
                Gene[] children = reproduce(parents[0], parents[1]);
            
                newcomers[numberOfChildren] = children[0];
                newcomers[numberOfChildren+1] = children[1];
            
                numberOfChildren+=2;
            }
        
            for(int i=0; i<POPULATION_SIZE; i++){
                getGene(i).setChromosome(newcomers[i].getChromosome());
            }
        }
    }
    
    Gene getGene(int index){ return m_population.get(index);}
    int getSize(){ return m_population.size();}
    
    Gene[] reproduce(Gene dad, Gene mom){        
        return crossOver(mom, dad);
    }
    
    Gene getNextGene()
    {
     
       return m_population.get(NextGene++);
    }
    
    //multipoint cross (currently 1 and 2 works)
    Gene[] crossOver(Gene mom, Gene dad){
        Gene[] babies = new Gene[2];
        
        if( (new Random().nextFloat() > CROSSOVER_RATE) || (mom == dad)){
            babies[0] = dad;
            babies[1] = mom;
            
            return babies;
        }
        
        switch(crossoverMode){
            case SINGLE_POINT:
                int randomPick = new Random().nextInt(Gene.CHROMOSOME_SIZE-1);
        
                for(int i=0; i<Gene.CHROMOSOME_SIZE; i++){
                    if(i < randomPick){
                        babies[0].setChromosomeElement(i, mom.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, dad.getChromosomeElement(i));
                    } else {
                        babies[0].setChromosomeElement(i, dad.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, mom.getChromosomeElement(i));
                    }
                }
                break;
                
            case TWO_POINT:
                int randomPick1 = new Random().nextInt(Gene.CHROMOSOME_SIZE-1);
                //pick a random number between last pick and length of chromosome
                int randomPick2 = new Random().nextInt((Gene.CHROMOSOME_SIZE-1) - (randomPick1 +1)) + randomPick1;
                
                for(int i=0; i<Gene.CHROMOSOME_SIZE; i++){
                    if(i > randomPick1 && i < randomPick2){
                        babies[0].setChromosomeElement(i, mom.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, dad.getChromosomeElement(i));
                    } else {
                        babies[0].setChromosomeElement(i, dad.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, mom.getChromosomeElement(i));
                    }
                }
                break;
                
            case MULTI_POINT:
                for(int i=0; i<Gene.CHROMOSOME_SIZE; i++){
                    if(new Random().nextFloat() < CROSSOVER_RATE){
                        babies[0].setChromosomeElement(i, mom.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, dad.getChromosomeElement(i));
                    } else {
                        babies[0].setChromosomeElement(i, dad.getChromosomeElement(i));
                        babies[1].setChromosomeElement(i, mom.getChromosomeElement(i));
                    }
                }
                break;
        }
        
        return babies;
    }
    
    Gene rouletteWheelSelection(){
        Collections.sort(m_population);
        double slice = new Random().nextFloat() * m_totalFitnessScore;
        double fitnessSum = 0;
        int selection = 0;
        
        for(int i=0; i<POPULATION_SIZE; i++){
            fitnessSum += m_population.get(i).getFitness();
            
            if(fitnessSum > slice)
                return m_population.get(i);
        }
        
        return m_population.get(selection);
    }
    
    Gene tournamentSelection(int count){
        double bestFitness = 0;
        int selection = 0;
        
        for(int i=0; i<count; i++){
            int randomSelection = new Random().nextInt(POPULATION_SIZE-1);
            
            if(getGene(randomSelection).getFitness() > bestFitness){
                selection = randomSelection;
                bestFitness = getGene(randomSelection).getFitness();
            }
        }
        
        return getGene(selection);
    }
    
    void elitism(){
        Collections.sort(m_population);
        int numberOfChildren = 0;
        Gene[] newcomers = new Gene[POPULATION_SIZE/2];
        
        //destroy the bad seeds:
        for(int i=0; i<POPULATION_SIZE/2; i++)
            m_population.remove(0);
        
        //create new children from the good half!
        while(numberOfChildren < POPULATION_SIZE / 2){
            
            Gene[] parents = {m_population.get(numberOfChildren), m_population.get(numberOfChildren+1)}; 
            Gene[] children = reproduce(parents[0], parents[1]);
            
            newcomers[numberOfChildren] = children[0];
            newcomers[numberOfChildren+1] = children[1];
            
            numberOfChildren+=2;
        }
        
        for(int i=0; i<POPULATION_SIZE/2; i++){
            m_population.add(newcomers[i]);
        }
    }
    
    /**
     * Main function for the GeneManager. 
     * This is where it all takes place.
     */
    public void Update(){
        int generation = 0;
        GeneManager population = new GeneManager();
        
        while(generation < MAX_GENERATIONS){
            population.evaluateGeneration();
            
            float avgFitness = 0f;
            float minFitness = Float.POSITIVE_INFINITY;
            float maxFitness = Float.NEGATIVE_INFINITY;
            String bestGene = "";
            String worstGene = "";
            
            for(int i=0; i<GeneManager.POPULATION_SIZE; i++){
                float currentFitness = population.getGene(i).getFitness();
                avgFitness += currentFitness;
                if(currentFitness < minFitness){
                    minFitness = currentFitness;
                    worstGene = population.getGene(i).getPhenotype();
                }
                
                if(currentFitness > maxFitness){
                    maxFitness = currentFitness;
                    bestGene = population.getGene(i).getPhenotype();
                }
            }
            
            if(population.getSize() > 0)
                avgFitness /= population.getSize();
            
            if(outputFitness){
                String output ="Generation #"+generation;
                output += "\tAverage fitness: "+avgFitness;
                output += "\n\tLowest fitness: "+minFitness + " |p: " + worstGene;
                output += "\n\tHighest fitness: "+maxFitness +" |p: " + bestGene;
                System.out.println(output);
            }
            
            population.produceNextGeneration();
            generation++;
        }
    }    
}
